﻿using System.Diagnostics.Contracts;
using System.IO.IsolatedStorage;
using System.Windows;

namespace DaveSexton.Labs
{
	/// <summary>
	/// Represents the out-of-browser window settings for a Silverlight host.
	/// </summary>
	public sealed class SilverlightWindowSettings
	{
		#region Public Properties
		/// <summary>
		/// The default singleton <see cref="SilverlightWindowSettings"/>.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
			Justification = "The designer for ApplicationSettingsBase also uses a mutable singleton.  Keeping the same for consistency.")]
		public static readonly SilverlightWindowSettings Default = new SilverlightWindowSettings();

		/// <summary>
		/// Gets or sets the location of the window.
		/// </summary>
		public Rect Location
		{
			get
			{
				if (settings.Contains(locationSetting))
				{
					var value = settings[locationSetting];

					Contract.Assume(value is Rect);

					return (Rect)value;
				}
				else
				{
					// To keep this code consistent with the WPF code, do not return Rect.Empty.
					return new Rect();
				}
			}
			set
			{
				settings[locationSetting] = value;
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="WindowState"/> of the window.
		/// </summary>
		public WindowState State
		{
			get
			{
				if (settings.Contains(stateSetting))
				{
					var value = settings[stateSetting];

					Contract.Assume(value is WindowState);

					return (WindowState)value;
				}
				else
				{
					return WindowState.Normal;
				}
			}
			set
			{
				settings[stateSetting] = value;
			}
		}
		#endregion

		#region Private / Protected
		private const string locationSetting = "MainWindow.WindowLocation";
		private const string stateSetting = "MainWindow.WindowState";
		private const int minDefaultWidth = 640;
		private const int minDefaultHeight = 400;

		private readonly IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
		private Window trackedWindow;
		#endregion

		#region Constructors
		private SilverlightWindowSettings()
		{
			Contract.Assume(settings != null);

			var app = Application.Current;

			if (app != null)
			{
				app.Exit += (sender, e) =>
				{
					if (trackedWindow != null)
					{
						Save();
					}
				};
			}
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(settings != null);
		}

		/// <summary>
		/// Saves the current state of the window.
		/// </summary>
		public void Save()
		{
			if (trackedWindow != null)
			{
				/* In testing, restoring the window size to the full screen or to the size of the minimized window causes the application 
				 * to stop loading immediately, without showing any errors.  For this reason, only save the location when the window state
				 * is normal.  Unfortunately, since Silverlight doesn't expose events that are required to track the current location, it's
				 * possible that the next time the application is executed the window will be shown at the previously saved location, even 
				 * if it was moved before being maximized or minimized.
				 */
				if (trackedWindow.WindowState == WindowState.Normal)
				{
					Location = new Rect(trackedWindow.Left, trackedWindow.Top, trackedWindow.Width, trackedWindow.Height);
				}

				if (trackedWindow.WindowState != WindowState.Minimized)
				{
					State = trackedWindow.WindowState;
				}
				else
				{
					State = WindowState.Normal;
				}
			}

			settings.Save();
		}

		internal void Track(Window window)
		{
			Contract.Requires(window != null);

			trackedWindow = window;

			SetLocation(trackedWindow, Location);

			/* Must invoke later to give the window time to move to its new location; otherwise, it could be maximized on 
			 * the wrong screen when there are multiple displays.  This occurred in testing.
			 */
			trackedWindow.Dispatcher.BeginInvoke(() =>
				{
					trackedWindow.WindowState = State;
				});
		}

		private static void SetLocation(Window window, Rect restoreTo)
		{
			Contract.Requires(window != null);

			// The default value is {0,0,0,0}, which is not actually equal to Rect.Empty.
			if (restoreTo == new Rect())
			{
				if (window.Width < minDefaultWidth)
				{
					window.Width = minDefaultWidth;
				}

				if (window.Height < minDefaultHeight)
				{
					window.Height = minDefaultHeight;
				}
			}
			else
			{
				/* Testing has shown that Silverlight automatically changes negative values to zero when any of these 
				 * properties are set, even though their getters will return negative values in multi-display scenarios.
				 */
				window.Top = restoreTo.Top;
				window.Left = restoreTo.Left;
				window.Width = restoreTo.Width;
				window.Height = restoreTo.Height;
			}
		}
		#endregion
	}
}